/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.module.clinicalsummary;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.security.AlgorithmParameters;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.api.context.Context;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.util.OpenmrsUtil;

/**
 * 
 * Utility class to perform file system operation for the download and upload process
 */
@Deprecated
public class FileUtils {
	
	private static final Log log = LogFactory.getLog(FileUtils.class);
	
	/**
	 * Secret key factory algorith.
	 * PBKDF2WithHmacSHA1 = Password Based Key Derivation Function Hash Message Authentication Code(HMAC)-SHA1
	 */
	private static final String SECRET_KEY_FACTORY = "PBKDF2WithHmacSHA1";
	
	/**
	 * Cipher configuration: algorithm name/mode/padding. Check the JCE specs for full documentation
	 */
	private static final String CIPHER_CONFIGURATION = "AES/CBC/PKCS5Padding";
	
	/**
	 * Encoding of the encryption key
	 */
	private static final String KEY_SPEC = "AES";
	
	/**
	 * Buffer size to process the upload and download
	 */
	private static final int BUFFER = 4096;
	
	/**
	 * @param outputStream
	 * @param path
	 * @param password
	 * @param passphrase
	 * @param ivName
	 * @throws Exception
	 */
	public static void deflateSummaries(OutputStream outputStream, String path, String password, String passphrase, String ivName) throws Exception {
		SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY);
		KeySpec spec = new PBEKeySpec(password.toCharArray(), passphrase.getBytes(), 1024, 128);
		SecretKey tmp = factory.generateSecret(spec);
		
		if (log.isDebugEnabled())
			log.debug("Secret Key Length: " + tmp.getEncoded().length);
		
		SecretKey secret = new SecretKeySpec(tmp.getEncoded(), KEY_SPEC);
		
		Cipher cipher = Cipher.getInstance(CIPHER_CONFIGURATION);
		cipher.init(Cipher.ENCRYPT_MODE, secret);
		
		// write the iv file
		AlgorithmParameters params = cipher.getParameters();
		byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
		File ivParent = OpenmrsUtil.getDirectoryInApplicationDataDirectory(SummaryConstants.ENCRYPTION_LOCATION);
		File ivFile = new File(ivParent, ivName);
		FileOutputStream fos = new FileOutputStream(ivFile);
		fos.write(iv);
		fos.close();
		
		ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(outputStream));
		
		byte data[] = new byte[BUFFER];
		
		File parent = OpenmrsUtil.getDirectoryInApplicationDataDirectory(path);
		String files[] = parent.list();
		
		CipherInputStream origin = null;
		for (String filename : files) {
			
			if (log.isDebugEnabled())
				log.debug("Deflating: " + filename);
			
			File file = new File(parent, filename);
			FileInputStream fi = new FileInputStream(file);
			origin = new CipherInputStream(fi, cipher);
			
			ZipEntry entry = new ZipEntry(file.getName());
			out.putNextEntry(entry);
			
			int count;
			while ((count = origin.read(data, 0, BUFFER)) != -1) {
				out.write(data, 0, count);
			}
			
			origin.close();
		}
		
		out.close();
	}
	
	public static void inflateSummaries(InputStream inputStream, String path, String password, String passphrase, byte[] iv) throws Exception {
		SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY);
		KeySpec spec = new PBEKeySpec(password.toCharArray(), passphrase.getBytes(), 1024, 128);
		SecretKey tmp = factory.generateSecret(spec);
		
		if (log.isDebugEnabled())
			log.debug("Secret Key Length: " + tmp.getEncoded().length);
		
		SecretKey secret = new SecretKeySpec(tmp.getEncoded(), KEY_SPEC);
		
		Cipher cipher = Cipher.getInstance(CIPHER_CONFIGURATION);
		cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
		
		ZipInputStream zis = new ZipInputStream(new BufferedInputStream(inputStream));
		
		byte data[] = new byte[BUFFER];
		
		File parent = OpenmrsUtil.getDirectoryInApplicationDataDirectory(path);
		
		ZipEntry entry;
		CipherOutputStream dest = null;
		while ((entry = zis.getNextEntry()) != null) {
			
			File file = new File(parent, entry.getName());
			FileOutputStream fos = new FileOutputStream(file);
			dest = new CipherOutputStream(fos, cipher);
			
			if (log.isDebugEnabled())
				log.debug("Inflating: " + file.getName());
			
			int count;
			while ((count = zis.read(data, 0, BUFFER)) != -1) {
				dest.write(data, 0, count);
			}
			
			dest.flush();
			dest.close();
		}
		
		zis.close();
	}
	
	private static String[] getConnectionProperties() {
		Properties props = Context.getRuntimeProperties();
		
		// username, password, database
		String[] connProps = { "test", "test", "openmrs" };
		
		String username = (String) props.get("database.username");
		if (username == null)
			username = (String) props.get("connection.username");
		if (username != null)
			connProps[0] = username;
		
		String password = (String) props.get("database.password");
		if (password == null)
			password = (String) props.get("connection.password");
		if (password != null)
			connProps[1] = password;
		
		// get database name
		String database = "openmrs";
		String connectionUrl = (String) props.get("connection.url");
		if (connectionUrl == null)
			connectionUrl = (String) props.get("connection.url");
		if (connectionUrl != null) {
			int qmark = connectionUrl.lastIndexOf("?");
			int slash = connectionUrl.lastIndexOf("/");
			database = connectionUrl.substring(slash + 1, qmark);
			connProps[2] = database;
		}
		
		return connProps;
	}
	
	public static void loadIndexFile(String filename) throws Exception {
		
		File outFolder = OpenmrsUtil.getDirectoryInApplicationDataDirectory(SummaryConstants.GENERATED_PDF_LOCATION);
		File indexFile = new File(outFolder, filename);
		
		String[] props = getConnectionProperties();
		String username = props[0];
		String password = props[1];
		String database = props[2];
    	
    	String path = indexFile.getAbsolutePath();
    	path = path.replace("\\", "/"); // replace windows file separator with forward slash
		
		String[] commands = { "mysql" ,
				              "-e",
				              "source " + path,
				              "-f",
				              "-u" + username,
				              "-p" + password,
				              "-D" + database
				            };

		String output;
    	if (OpenmrsConstants.UNIX_BASED_OPERATING_SYSTEM)
    		output = execCmd(outFolder, commands);
		else
			output = execCmd(null, commands);
		
		if (output != null && output.length() > 0) {
			log.error("Exec call: " + Arrays.asList(commands));
			log.error("Output of exec: " + output);
		}
	}
	
	public static void createIndexFile(String filename) throws Exception {
		String[] props = getConnectionProperties();
		String username = props[0];
		String password = props[1];
		String database = props[2];
		
		File outFolder = OpenmrsUtil.getDirectoryInApplicationDataDirectory(SummaryConstants.GENERATED_PDF_LOCATION);
		File indexFile = new File(outFolder, filename);
		String[] tables = {"clinical_summary",
						   "clinical_summary_index",
						   "clinical_summary_mapping",
						   "location",
				           "patient_identifier_type",
				           "person",
				           "patient",
				           "patient_identifier",
				           "person_name",
				           "person_address",
				           "privilege",
				           "role", 
				           "role_privilege", 
				           "role_role", 
				           "user_group",
				           "user_property",
				           "user_role", 
				           "users"
				         };
		
		String[] commands = { "mysqldump",
				              "-u" + username,
				              "-p" + password,
				              "-x",
				              "-q",
				              "-e",
				              "--add-drop-table",
				              "-r",
				              indexFile.getAbsolutePath(),
				              database
				            };
		
		commands = (String[]) ArrayUtils.addAll(commands, tables);
		
		String output;
    	if (OpenmrsConstants.UNIX_BASED_OPERATING_SYSTEM)
    		output = execCmd(outFolder, commands);
		else
			output = execCmd(null, commands);
		
		if (output != null && output.length() > 0) {
			log.debug("Exec called: " + Arrays.toString(commands));
			log.debug("Output of exec: " + output);
		}
	}
	
	/**
	 * 
	 * 
	 * @param wd
	 * @param cmdWithArguments
	 * @return
	 * @throws Exception
	 */
	private static String execCmd(File wd, String[] cmdWithArguments) throws Exception {
		StringBuffer out = new StringBuffer();
		// Needed to add support for working directory because of a linux file system permission issue.
		Process p = (wd != null) ? Runtime.getRuntime().exec(cmdWithArguments, null, wd) : Runtime.getRuntime().exec(
		    cmdWithArguments);
		
		out.append("Normal cmd output:\n");
		Reader reader = new InputStreamReader(p.getInputStream());
		BufferedReader input = new BufferedReader(reader);
		int readChar = 0;
		while ((readChar = input.read()) != -1) {
			out.append((char) readChar);
		}
		input.close();
		reader.close();
		
		out.append("ErrorStream cmd output:\n");
		reader = new InputStreamReader(p.getErrorStream());
		input = new BufferedReader(reader);
		readChar = 0;
		while ((readChar = input.read()) != -1) {
			out.append((char) readChar);
		}
		input.close();
		reader.close();
		
		if (p.waitFor() != 0)
			log.error("exit value = " + p.exitValue());

		return out.toString();
	}
}
